package p24_ControllerAdvice之InitBinder;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 两种InitBinder，加载ControllerAdvice和加载控制器类内部方法，他们的执行顺序
 *  1.@ControllerAdvice里面的@InitBinder在RequestMappingHandlerAdapter初始化时就解析好全部放入缓存
 *  2.@Controller里面的@InitBinder，在第一次访问控制器方法时，解析对应的@InitBinder并放入缓存
 * 处理器适配器内部，会维护两个Map集合用来缓存这两种InitBinder
 * 本节知识点：
 *  绑定前工厂的扩展点(advice之一)，通过@InitBinder扩展类型转换器
 */
@Slf4j
public class _InitBinder {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        //手动创建RequestMappingHandlerAdapter
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        adapter.setApplicationContext(context);
        //这一步，会解析全部@ControllerAdvice里面的@InitBinder放入缓存
        adapter.afterPropertiesSet();
        log.info("刚开始.....");
        showBindMethods(adapter);

        Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
        getDataBinderFactory.setAccessible(true);

        /**
         * 通过调用RequestMappingHandlerAdapter的getDataBinderFactory方法，就会调用到目标控制器方法
         */
        log.info("调用controller1.....");
        getDataBinderFactory.invoke(adapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
        showBindMethods(adapter);

        log.info("调用controller2.....");
        getDataBinderFactory.invoke(adapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("foo")));
        showBindMethods(adapter);

        log.info("再次调用controller2.....");
        getDataBinderFactory.invoke(adapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("foo")));
        showBindMethods(adapter);

    }

    public static void showBindMethods(RequestMappingHandlerAdapter adapter) throws Exception {
        //打印Advice类型的InitBinder
        Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
        initBinderAdviceCache.setAccessible(true);
        Map<ControllerAdviceBean, Set<Method>> adviceBeanSetMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(adapter);
        String binders = adviceBeanSetMap.values().stream().flatMap(set -> set.stream()).map(m -> m.getName()).collect(Collectors.joining(", "));
        log.info("  ===>全局的：[{}]", binders);

        //打印控制器内部的InitBinder
        Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
        initBinderCache.setAccessible(true);
        Map<ControllerAdviceBean, Set<Method>> beanSetMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderCache.get(adapter);
        binders = beanSetMap.values().stream().flatMap(set -> set.stream()).map(m -> m.getName()).collect(Collectors.joining(", "));
        log.info("  ===>控制器的：[{}]", binders);
    }

}
